package com.superbytecode.cloud.redis;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.superbytecode.cloud.redis.aspect.RedisCacheExpireAspect;
import com.superbytecode.cloud.redis.client.RedisTemplateClient;
import com.superbytecode.cloud.redis.wrapper.RedisCacheWriter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;

import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * All rights Reserved, Designed By www.super-master.com
 *
 * @ProjectName: sanskrit
 * @Package: com.superbytecode.cloud.redis
 * @ClassName: RedisCacheAutoConfiguration
 * @Description: []
 * <p> </p>
 * @Author: [Lance Ting]
 * @Date: 2024/8/19 11:09
 * @Version: V1.0
 * @Copyright: 2024 www.super-master.cn Inc. All rights reserved.
 * TODO: 注意,本文件Lance Ting所作,如果转载或使用请标明具体出处!
 **/
@EnableCaching
@Configuration
@Import({RedisTemplateClient.class, RedisCacheExpireAspect.class})
public class RedisCacheAutoConfiguration extends CachingConfigurerSupport{

    @Value("${spring.application.name:unknown}")
    private String applicationName;

    /**
     * 容器中注入RedisTemplate对象，设置序列化方式
     * <p>
     * tips：泛型可以适用于各种类型的注入
     *
     * @param factory         redis链接工厂
     * @param redisSerializer redis序列化器
     * @param <T>             value类型
     * @return RedisTemplate
     */
    @Bean
    public <T> RedisTemplate<String, T> redisTemplate(RedisConnectionFactory factory, RedisSerializer<?> redisSerializer) {
        // 指定序列化方式
        RedisTemplate<String, T> redisTemplate = new RedisTemplate<>();
        redisTemplate.setKeySerializer(RedisSerializer.string());
        redisTemplate.setHashKeySerializer(RedisSerializer.string());
        redisTemplate.setValueSerializer(redisSerializer);
        redisTemplate.setHashValueSerializer(redisSerializer);
        redisTemplate.setConnectionFactory(factory);
        return redisTemplate;
    }

    /**
     * 容器中注入RedisCacheManager容器，并设置序列化方式
     *
     * @param factory         redis链接工厂
     * @param redisSerializer redis序列化器
     * @return CacheManager
     */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory, RedisSerializer<Object> redisSerializer) {
        RedisCacheConfiguration config = RedisCacheConfiguration
                // 默认配置允许缓存null
                .defaultCacheConfig()
                // 接口缓存上指定了key的时候统一加服务名前缀
                .computePrefixWith(cacheName -> applicationName + "#" + cacheName + ":")
                // 可以根据业务需要设置统一过期时间，这里是为了强制使用@CacheExpire手动设置过期时间所以设置很短
                .entryTtl(Duration.ofMinutes(30))
                // 配置序列化和反序列化方式
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.string()))//
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer));
        return RedisCacheManager.builder(new RedisCacheWriter(factory)).cacheDefaults(config).build();
    }

    /**
     * 定义key生成器，使用@Cachable生成的key的默认规则
     * <br>
     * 默认的是 SimpleKeyGenerator, 它使用方法参数组合生成的一个 key, 这里存在一个问题:
     * 如果2个方法, 参数是一样的. 但执行逻辑不同, 那么将会导致执行第二个方法时命中第一个方法的缓存. 所以, 通常需要自定义.
     * <p>
     * 格式：入参1,入参2
     * 示例：1,2
     * 加上前缀后完整key格式：xxx_service#xxx:1,2
     * </p>
     * 建议使用@Cacheable的时候都指定key
     *
     * @return KeyGenerator
     */
    @Bean
    @Override
    public KeyGenerator keyGenerator() {
        // 类名、方法名、参数列表
        // 参数列表使用“,”进行分割
        return (target, method, params) -> Stream.of(params).map(String::valueOf).collect(Collectors.joining(","));
    }

    /**
     * Jackson序列化器
     * <p>
     * 初始化Redis序列器，使用jackson
     *
     * @return RedisSerializer<Object>
     */
    @Bean
    public RedisSerializer<Object> redisSerializer() {
        ObjectMapper objectMapper = new ObjectMapper();
        // jdk日期格式支持
        DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        //
        LocalDateDeserializer localDateDeserializer = new LocalDateDeserializer(dateFormatter);
        LocalDateSerializer localDateSerializer = new LocalDateSerializer(dateFormatter);
        LocalDateTimeDeserializer localDateTimeDeserializer = new LocalDateTimeDeserializer(timeFormatter);
        LocalDateTimeSerializer localDateTimeSerializer = new LocalDateTimeSerializer(timeFormatter);
        Module javaTimeModule = new JavaTimeModule()//
                .addDeserializer(LocalDate.class, localDateDeserializer)//
                .addDeserializer(LocalDateTime.class, localDateTimeDeserializer)//
                .addSerializer(LocalDate.class, localDateSerializer)//
                .addSerializer(LocalDateTime.class, localDateTimeSerializer);
        //
        objectMapper.registerModule(javaTimeModule);
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // 序列化时将对象全类名一起保存下来
        /*
         * 不设置objectMapper.activateDefaultTyping：将无法反序列化
         * {"id":1000,"name":"张三"}
         */
        // objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
        /*
         * 设置objectMapper.activateDefaultTyping：
         *  ["com.superbytecode.sample.entity.User",{"id":1000,"name":"张三"}]
         */
        Jackson2JsonRedisSerializer<Object> redisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        redisSerializer.setObjectMapper(objectMapper);
        return redisSerializer;
    }
}